package codename.carrot.bomberman.network;

import java.util.ArrayList;

import android.os.Handler;
import android.os.Message;
import android.util.Log;
import codename.carrot.bomberman.network.NetworkConnection.NetworkState;
import codename.carrot.bomberman.network.events.ReceiveEvent;
import codename.carrot.bomberman.network.interfaces.NetworkMessage;
import codename.carrot.bomberman.network.listener.NetworkReceiveListener;
import codename.carrot.bomberman.network.listener.NetworkServerReceiveListener;
import codename.carrot.bomberman.network.listener.NetworkStateListener;

/**
 * This class extends the Android Handler and is used as a message buffer to
 * obtain messages received from the network and then forward to models in the
 * game.
 * 
 * @author Group A4.
 */
public class MessageHandler extends Handler {
	/**
	 * String for debugging.
	 */
	private String DEBUG = this.getClass().getSimpleName();

	/**
	 * Messages for the server side. ArrayList of listeners for receive events
	 * from the network.
	 */
	private ArrayList<NetworkServerReceiveListener> serverReceiveListeners = new ArrayList<NetworkServerReceiveListener>();
	/**
	 * Messages for the client side. ArrayList of listeners for receive events
	 * from the network.
	 */
	private ArrayList<NetworkReceiveListener> receiveListeners = new ArrayList<NetworkReceiveListener>();
	/**
	 * ArrayList of listeners for state of the network connection.
	 */
	private ArrayList<NetworkStateListener> stateListener = new ArrayList<NetworkStateListener>();

	public enum MessageType {
		/**
		 * For state messages.
		 */
		STATE,
		/**
		 * For receive messages.
		 */
		RECEIVE
	};

	public enum MessageReceiver {
		/**
		 * Include all connections (Send to all connections).
		 */
		INCLUSIVE,
		/**
		 * Exclude one connection. (Send to all, except one).
		 */
		EXCLUSIVE,
		/**
		 * Send an independent message. (Send to only one connection).
		 */
		INDEPENDENT
	};

	/**
	 * Alert network state listeners of a change in the network state.
	 * 
	 * @param state
	 *            The message to distribute.
	 */
	private void fireStateEvent(Message state) {
		NetworkState ns = (NetworkState) state.obj;
		for (NetworkStateListener nsl : stateListener) {
			nsl.onStateChange(ns);
		}
	}

	/**
	 * Add a listener for network state events.
	 * 
	 * @param nsl
	 *            The listener to be added.
	 */
	public void addStateListener(NetworkStateListener nsl) {
		stateListener.add(nsl);
	}

	/**
	 * Remove a listener for network state events.
	 * 
	 * @param nsl
	 *            The listener to be removed.
	 */
	public void removeStateListener(NetworkStateListener nsl) {
		stateListener.remove(nsl);
	}

	/**
	 * Add a listener for network received events targeted the client side.
	 * 
	 * @param nrl
	 *            The listener to be added.
	 */
	public void addReceiveListener(NetworkReceiveListener nrl) {
		receiveListeners.add(nrl);
	}

	/**
	 * Remove a listener for network received events targeted the client side.
	 * 
	 * @param nrl
	 *            The listener to be removed.
	 */
	public void removeReceiveListener(NetworkReceiveListener nrl) {
		serverReceiveListeners.remove(nrl);
	}

	/**
	 * Add a listener for receive events targeted the server side.
	 * 
	 * @param nsrl
	 *            The listener to be added.
	 */
	public void addServerReceiveListener(NetworkServerReceiveListener nsrl) {
		serverReceiveListeners.add(nsrl);
	}

	/**
	 * Remove a listener for network received events targeted the server side.
	 * 
	 * @param nsrl
	 *            The listener to be removed.
	 */
	public void removeServerReceiveListener(NetworkServerReceiveListener nsrl) {
		receiveListeners.remove(nsrl);
	}

	/**
	 * Obtain a message from the network connection. - This method is usually
	 * used for network state events.
	 * 
	 * @param message
	 *            The type of message (STATE).
	 * @param obj
	 *            The message.
	 * @return A Message from the global message pool.
	 */
	public Message obtainMessage(MessageType message, Object obj) {
		return obtainMessage(message.ordinal(), obj);
	}

	/**
	 * Obatain a message from the network connection. - This method is used for
	 * receive event.
	 * 
	 * @param message
	 *            The type of message (RECEIVE);
	 * @param clientid
	 *            The id of the connection where it came from.
	 * @param obj
	 *            The message.
	 * @return A Message from the global message pool.
	 */
	public Message obtainMessage(MessageType message, int clientid, Object obj) {
		return obtainMessage(message.ordinal(), clientid, -1, obj);
	}

	/**
	 * Sort messages and alert listeners. If the message is obtained from
	 * connection 0, the server receive listeners are alerted, else the client
	 * receive listeners are alerted.
	 * 
	 * @param message
	 *            The message to distribute.
	 * @param clientid
	 *            The id of the connection the message came from.
	 */
	private void fireReceiveEvent(Message message, int clientid) {
		if (clientid == 0) {
			if (!(message.obj instanceof NetworkMessage))
				return;
			NetworkMessage nm = (NetworkMessage) message.obj;
			Log.d(DEBUG, "TO CLIENT - Received: "
					+ nm.getClass().getSimpleName());
			for (NetworkReceiveListener nrl : receiveListeners) {
				nrl.onMessageReceive(new ReceiveEvent(nm, clientid));
			}
		} else {
			if (!(message.obj instanceof NetworkMessage))
				return;
			NetworkMessage nm = (NetworkMessage) message.obj;
			Log.d(DEBUG, "TO SERVER - Received: "
					+ nm.getClass().getSimpleName());
			for (NetworkServerReceiveListener nsrl : serverReceiveListeners) {
				nsrl.onMessageReceive(new ReceiveEvent(nm, clientid));
			}
		}
	}

	/**
	 * Returns the message type based on the msg.what integer.
	 * 
	 * @param ordinal
	 *            The msg.what integer.
	 * @return The message type
	 */
	public MessageType getMessageType(int ordinal) {
		return MessageType.values()[ordinal];
	}

	/**
	 * Sort messages to listener object on receive.
	 * 
	 * @see android.os.Handler#handleMessage(Message)
	 */
	@Override
	public void handleMessage(Message msg) {
		switch (getMessageType(msg.what)) {
		case RECEIVE:
			Log.d(DEBUG, "Message from Client: " + msg.arg1);
			fireReceiveEvent(msg, msg.arg1);
			break;
		case STATE:
			fireStateEvent(msg);
			break;
		}
	}
}
